{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "github地址:[https://github.com/cheesezh/python_design_patterns](https://github.com/cheesezh/python_design_patterns)\n",
    "\n",
    "## 题目\n",
    "\n",
    "如何让一个程序，可以灵活替换数据库？\n",
    "\n",
    "## 基础版本"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "向SQL Server中添加一个User\n",
      "从SQL Server中搜索User 1\n"
     ]
    }
   ],
   "source": [
    "class User():\n",
    "    \"\"\"\n",
    "    用户类，模拟用户表，假设只有ID和name两个字段\n",
    "    \"\"\"\n",
    "    def __init__(self):\n",
    "        self.id = None\n",
    "        self.name = None\n",
    "        \n",
    "\n",
    "class SqlServerUser():\n",
    "    \"\"\"\n",
    "    sqlserveruser类，用于操作User表\n",
    "    \"\"\"\n",
    "    def insert(self, user):\n",
    "        print(\"向SQL Server中添加一个User\")\n",
    "        \n",
    "    def get_user(self, id):\n",
    "        print(\"从SQL Server中搜索User\", id)\n",
    "        \n",
    "        \n",
    "def main():\n",
    "    user = User()\n",
    "    \n",
    "    su = SqlServerUser()\n",
    "    su.insert(user)\n",
    "    su.get_user(1)\n",
    "    \n",
    "main()\n",
    "    "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 点评\n",
    "这里之所以不能灵活更换数据库，是因为`su = SqlServerUser()` 将客户端和SQL Server绑定在一起，如果这里是“多态的”，那么就不需要考虑是SQL Server还是Access了。\n",
    "\n",
    "这里可以用“工厂方法模式”改进，工厂方法模式是定义一个用于创建对象的接口，让子类决定实例化哪一个类。\n",
    "\n",
    "## 改进版本1.0——工厂方法模式"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "在SQL Server中添加一个User\n",
      "从SQL Server中搜索User 1\n"
     ]
    }
   ],
   "source": [
    "from abc import ABCMeta, abstractmethod\n",
    "\n",
    "class IUser():\n",
    "    __metaclass__ = ABCMeta\n",
    "    \n",
    "    @abstractmethod\n",
    "    def insert(self, user):\n",
    "        pass\n",
    "    \n",
    "    @abstractmethod\n",
    "    def get_user(self, id):\n",
    "        pass\n",
    "    \n",
    "    \n",
    "class SqlServerUser(IUser):\n",
    "    \n",
    "    def insert(self, user):\n",
    "        print(\"在SQL Server中添加一个User\")\n",
    "        \n",
    "    def get_user(self, id):\n",
    "        print(\"从SQL Server中搜索User\", id)\n",
    "        \n",
    "        \n",
    "class AccessUser(IUser):\n",
    "    \n",
    "    def insert(self, user):\n",
    "        print(\"在Access中添加一个User\")\n",
    "        \n",
    "    def get_user(self, id):\n",
    "        print(\"从Access中搜索User\", id)\n",
    "        \n",
    "    \n",
    "class IFactory():\n",
    "    __metaclass__ = ABCMeta\n",
    "    \n",
    "    @abstractmethod\n",
    "    def create_user(self):\n",
    "        pass\n",
    "    \n",
    "\n",
    "class SqlServerFactory(IFactory):\n",
    "    def create_user(self):\n",
    "        return SqlServerUser()\n",
    "    \n",
    "    \n",
    "class AccessFactory(IFactory):\n",
    "    def create_user(self):\n",
    "        return AccessUser()\n",
    "    \n",
    "    \n",
    "def main():\n",
    "    user = User()\n",
    "    factory = SqlServerFactory()\n",
    "    iuser = factory.create_user()\n",
    "\n",
    "    iuser.insert(user)\n",
    "    iuser.get_user(1)\n",
    "    \n",
    "main()\n",
    "    "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 点评\n",
    "\n",
    "现在如果要更换数据库，只需要把`factory = SqlServerFactory()`更改成`factory = AccessFactory()`即可。这里由于多态的关系，使得声明IUser接口的对象iuser事先并不知道在访问哪个数据库，却可以在运行时很好的完成工作，这就是业务逻辑与数据访问解耦。\n",
    "\n",
    "但是，数据库中不可能只有一个User表，还可能有其他表，比如Department，那就需要增加好多个新的类。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "在SQL Server中添加一个User\n",
      "从SQL Server中搜索User 1\n",
      "在SQL Server中添加一个Department\n",
      "从SQL Server中搜索Department 1\n"
     ]
    }
   ],
   "source": [
    "class Department():\n",
    "    def __init__(self):\n",
    "        self.id = None\n",
    "        self.name = None\n",
    "        \n",
    "        \n",
    "class IDepartment():\n",
    "    __metaclass__ = ABCMeta\n",
    "    \n",
    "    @abstractmethod\n",
    "    def insert(self, department):\n",
    "        pass\n",
    "    \n",
    "    @abstractmethod\n",
    "    def get_department(self, id):\n",
    "        pass\n",
    "    \n",
    "    \n",
    "class SqlServerDepartment(IDepartment):\n",
    "    def insert(self, department):\n",
    "        print(\"在SQL Server中添加一个Department\")\n",
    "        \n",
    "    def get_department(self, id):\n",
    "        print(\"从SQL Server中搜索Department\", id)\n",
    "        \n",
    "\n",
    "class AccessDepartment(IDepartment):\n",
    "    def insert(self, department):\n",
    "        print(\"在Access中添加一个Department\")\n",
    "        \n",
    "    def get_department(self, id):\n",
    "        print(\"从Access中搜索Department\", id)\n",
    "        \n",
    "        \n",
    "class IFactory():\n",
    "    __metaclass__ = ABCMeta\n",
    "    \n",
    "    @abstractmethod\n",
    "    def create_user(self):\n",
    "        pass\n",
    "    \n",
    "    @abstractmethod\n",
    "    def create_department(self):\n",
    "        pass\n",
    "    \n",
    "\n",
    "class SqlServerFactory(IFactory):\n",
    "    def create_user(self):\n",
    "        return SqlServerUser()\n",
    "    \n",
    "    def create_department(self):\n",
    "        return SqlServerDepartment()\n",
    "    \n",
    "    \n",
    "class AccessFactory(IFactory):\n",
    "    def create_user(self):\n",
    "        return AccessUser()\n",
    "    \n",
    "    def create_department(self):\n",
    "        return AccessDepartment()\n",
    "    \n",
    "    \n",
    "def main():\n",
    "    user = User()\n",
    "    dept = Department()\n",
    "    \n",
    "    factory = SqlServerFactory()\n",
    "    \n",
    "    iuser = factory.create_user()\n",
    "    iuser.insert(user)\n",
    "    iuser.get_user(1)\n",
    "    \n",
    "    idept = factory.create_department()\n",
    "    idept.insert(dept)\n",
    "    idept.get_department(1)\n",
    "    \n",
    "main()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 点评\n",
    "\n",
    "这样就可以做到，只需要更改`factory = SqlServerFactory()`，就可以随便切换数据库了。\n",
    "\n",
    "当只有一个User类和User操作类的时候，只需要工厂方法模式就可以了。但是数据库中显然有很多的表，而SQL Server和Acess又是两大不同的类，所以解决这种涉及多个产品系列的问题，就需要使用抽象工厂模式。\n",
    "\n",
    "## 抽象工厂模式\n",
    "\n",
    "抽象工厂模式，提供一个创建一系列相关或相互依赖对象的接口，而无需指定它们的具体类。\n",
    "\n",
    "在上述问题中：\n",
    "- User和Department相当于两个抽象产品；\n",
    "- SqlServerUser和AccessUser是抽象产品User的具体产品实现；\n",
    "- IFactory是一个抽象工厂接口，里边包含所有的产品创建的抽象方法；\n",
    "- SqlServerFactory和AccessFactory是具体工厂；\n",
    "\n",
    "通常的过程是，在运行时刻创建一个ConcretFactory类的实例，这个具体的工厂再创建具有特定实现的产品对象，也就是说，为创建不同的产品对象，客户端应使用不同的具体工厂。\n",
    "\n",
    "### 抽象工厂模式的优点是什么？\n",
    "\n",
    "最大的好处便是易于交换产品系列，由于具体工厂类在一个应用中只需要在初始化的时候出现一次，这就使得改变一个应用的具体工厂变得非常容易，它只需要改变具体工厂即可使用不同的产品配置。\n",
    "\n",
    "其次的好处就是让具体的创建实例过程与客户端分离，客户端是通过它们的抽象接口操纵实例，产品的具体类名也被具体工厂的实现分离，不会出现在客户端代码中。\n",
    "\n",
    "### 抽象工厂模式的缺点是什么？\n",
    "\n",
    "抽象工厂模式可以很方便地切换两个数据库的访问代码，但是当需要增加功能，比如增加项目表Project，那就需要增加三个类IProject，SqlServerProject，AccessProject，还要更改IFactory，SqlServerFactory和AccessFactory。如果有100个调用数据访问的类，那要更改100次才能切换数据库，这是非常丑陋的做法。\n",
    "\n",
    "## 用简单工厂改进抽象工厂\n",
    "\n",
    "去除IFactory，SqlServerFactory和AccessFactory三个工厂类，取而代之的是DataAccess类。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "在SQL Server中添加一个User\n",
      "从SQL Server中搜索User 1\n",
      "在SQL Server中添加一个Department\n",
      "从SQL Server中搜索Department 1\n"
     ]
    }
   ],
   "source": [
    "class DataAcess():\n",
    "    \n",
    "    # 类变量，通过`类名.变量名`访问\n",
    "    db = \"sql_server\"\n",
    "        \n",
    "    @classmethod\n",
    "    def create_user(self):\n",
    "        if DataAcess.db == \"sql_server\":\n",
    "            return SqlServerUser()\n",
    "        elif DataAcess.db == \"access\":\n",
    "            return AccessUser()\n",
    "\n",
    "    @classmethod\n",
    "    def create_department(self):\n",
    "        if DataAcess.db == \"sql_server\":\n",
    "            return SqlServerDepartment()\n",
    "        elif DataAcess.db == \"access\":\n",
    "            return AccessDepartment()\n",
    "        \n",
    "        \n",
    "def main():\n",
    "    user = User()\n",
    "    dept = Department()\n",
    "    \n",
    "    iu = DataAcess.create_user()\n",
    "    iu.insert(user)\n",
    "    iu.get_user(1)\n",
    "    \n",
    "    idept = DataAcess.create_department()\n",
    "    idept.insert(dept)\n",
    "    idept.get_department(1)\n",
    "    \n",
    "main()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 点评\n",
    "所有用到简单工厂的地方，都可以考虑使用反射技术来去除swith或if-else，接触分支带来的耦合。\n",
    "\n",
    "## 反射版本"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 20,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "在Access中添加一个User\n",
      "从Access中搜索User 1\n",
      "在Access中添加一个Department\n",
      "从Access中搜索Department 1\n"
     ]
    }
   ],
   "source": [
    "import sys\n",
    "\n",
    "def createInstance(module_name, class_name, *args, **kwargs):\n",
    "    class_meta = getattr(module_name, class_name)\n",
    "    obj = class_meta(*args, **kwargs)\n",
    "    return obj\n",
    "\n",
    "\n",
    "def main():\n",
    "    db = \"Access\"  # load from config file\n",
    "    user = User()\n",
    "    dept = Department()\n",
    "    \n",
    "    iuser = createInstance(sys.modules[__name__], \"{}User\".format(db))\n",
    "    iuser.insert(user)\n",
    "    iuser.get_user(1)\n",
    "    \n",
    "    idept = createInstance(sys.modules[__name__], \"{}Department\".format(db))\n",
    "    idept.insert(dept)\n",
    "    idept.get_department(1)\n",
    "    \n",
    "main()"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.6.4"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
